// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "../CasController.h"
#include "../CasCommon.h"
#include "CasMsgCode.h"
#include "CasLog.h"
#include "CasBuffer.h"
#include "CasMsg.h"
#include "CasVideoHDecodeThread.h"

using namespace std;

class CasFirstVideoFrameImpl : public CasFirstVideoFrameListener {
public:
    void OnFirstFrame() override
    {
        if (CasController::GetInstance() != nullptr) {
            CasController::GetInstance()->NotifyFirstVideoFrame();
        }
    }
};

class CasVideoDecodeStatImpl : public CasVideoDecodeStatListener {
public:
    void OnDecodeOneFrame(uint64_t decTime) override
    {
        // here can do some stat things.
    }
};

CasVideoHDecodeThread::CasVideoHDecodeThread(ANativeWindow *nativeWindow, FrameType frameType, int rotationDegrees)
{
    this->m_nativeWindow = nativeWindow;
    this->m_videoEngine = nullptr;
    this->m_firstVideoFrame = nullptr;
    this->m_videoDecodeStat = nullptr;
    this->m_videoPktStream = nullptr;
    this->m_decodeTask = nullptr;
    this->m_threadStatus = CAS_THREAD_INIT;
    this->m_frameType = frameType;
    this->m_rotationDegrees = rotationDegrees;
}

CasVideoHDecodeThread::~CasVideoHDecodeThread()
{
    if (this->m_videoEngine != nullptr) {
        delete this->m_videoEngine;
        this->m_videoEngine = nullptr;
    }

    if (this->m_firstVideoFrame != nullptr) {
        delete this->m_firstVideoFrame;
        this->m_firstVideoFrame = nullptr;
    }

    if (this->m_videoDecodeStat != nullptr) {
        delete this->m_videoDecodeStat;
        this->m_videoDecodeStat = nullptr;
    }

    if (this->m_decodeTask != nullptr) {
        delete this->m_decodeTask;
        this->m_decodeTask = nullptr;
    }

    this->m_videoPktStream = nullptr;
}

void CasVideoHDecodeThread::SetDecodePktHandle(CasDataPipe *videoPktStream)
{
    this->m_videoPktStream = videoPktStream;
}

CasDataPipe *CasVideoHDecodeThread::GetVideoPktStream()
{
    return this->m_videoPktStream;
}

CasVideoEngine *CasVideoHDecodeThread::GetVideoEngine()
{
    return this->m_videoEngine;
}

void CasVideoHDecodeThread::SetThreadStatus(int status)
{
    this->m_threadStatus = status;
}

int CasVideoHDecodeThread::GetThreadStatus()
{
    return this->m_threadStatus;
}

void DecodeTaskEntry(CasVideoHDecodeThread *decodeThreadObj)
{
    INFO("Decode task begin.");
    uint32_t startRet = decodeThreadObj->GetVideoEngine()->StartDecoder();
    if (startRet != 0) {
        ERR("Start error %u", startRet);
        return;
    }

    CasDataPipe *videoPktStream = decodeThreadObj->GetVideoPktStream();
    CasVideoEngine *videoEngine = decodeThreadObj->GetVideoEngine();
    bool lastFramePending = false;
    void *onePkt = nullptr;
    int32_t dataSize = 0;

    while (decodeThreadObj->GetThreadStatus() == CAS_THREAD_RUNNING ||
        decodeThreadObj->GetThreadStatus() == CAS_THREAD_STOP) {
        if (decodeThreadObj->GetThreadStatus() == CAS_THREAD_STOP) {
            usleep(100 * 1000);
            continue;
        }

        if (!lastFramePending) {
            int pktNum = videoPktStream->GetNumItems();
            if (pktNum == 0) {
                usleep(100);
                continue;
            } else {
                DBG("Number of packet is %d", pktNum);
            }

            onePkt = videoPktStream->GetNextPkt();
            if (onePkt == nullptr) {
                ERR("Next packet is nullptr.");
                continue;
            }

            dataSize = static_cast<int32_t>(((stream_msg_head_t *)onePkt)->GetPayloadSize());
            lastFramePending = true;
        }

        uint32_t ret = 0;
        ret = videoEngine->DecodeFrame((uint8_t *)onePkt + sizeof(stream_msg_head_t), dataSize);

        if (ret == SUCCESS) {
            lastFramePending = false;
            FreeBuffer(onePkt);
        } else if (ret == VIDEO_ENGINE_CLIENT_DECODE_ERR) {
            ERR("Client decode error.");
        } else if (ret == VIDEO_ENGINE_CLIENT_PARAM_INVALID) {
            ERR("Client param invalid.");
            break;
        } else {
            ERR("Task thread exited.");
            break;
        }
    }
    decodeThreadObj->SetThreadStatus(CAS_THREAD_INIT);
    INFO("Decode task end");
}

int CasVideoHDecodeThread::Start()
{
    if (this->m_threadStatus == CAS_THREAD_RUNNING) {
        return -1;
    }
    if (this->m_videoEngine != nullptr) {
        delete this->m_videoEngine;
        this->m_videoEngine = nullptr;
    }
    this->m_videoEngine = new (std::nothrow) CasVideoEngine();
    if (this->m_videoEngine == nullptr) {
        ERR("CasVideoEngine new, return nullptr.");
        return -1;
    }
    if (this->m_firstVideoFrame != nullptr) {
        delete this->m_firstVideoFrame;
        this->m_firstVideoFrame = nullptr;
    }
    if (this->m_videoDecodeStat != nullptr) {
        delete this->m_videoDecodeStat;
        this->m_videoDecodeStat = nullptr;
    }
    this->m_firstVideoFrame = new (std::nothrow) CasFirstVideoFrameImpl();
    if (this->m_firstVideoFrame == nullptr) {
        ERR("First video frame is null.");
        return -1;
    }
    this->m_videoEngine->SetFirstVidFrameListener(this->m_firstVideoFrame);
    this->m_videoDecodeStat = new (std::nothrow) CasVideoDecodeStatImpl();
    if (this->m_videoDecodeStat == nullptr) {
        ERR("Video decode stat is null.");
        return -1;
    }
    this->m_videoEngine->SetVideoDecodeStatListener(m_videoDecodeStat);

    uint32_t initRet = this->m_videoEngine->InitDecoder(this->m_nativeWindow, DecoderType::DECODER_TYPE_HW,
                                                        m_frameType, m_rotationDegrees);
    if (initRet != 0) {
        ERR("Init error %u", initRet);
        EngineStat status;
        if (this->m_videoEngine->GetDecoderStatus(status) == SUCCESS) {
            if (status == EngineStat::ENGINE_RUNNING) {
                this->m_videoEngine->StopDecoder();
                this->m_videoEngine->DestroyDecoder();
            } else if (status == EngineStat::ENGINE_STOP) {
                this->m_videoEngine->DestroyDecoder();
            }
        } else {
            INFO("Get decoder status error.");
            return initRet;
        }
        initRet = this->m_videoEngine->InitDecoder(this->m_nativeWindow, DecoderType::DECODER_TYPE_HW,
                                                   m_frameType, m_rotationDegrees);
        if (initRet != 0) {
            ERR("Init again return error %u.", initRet);
            return initRet;
        }
    }

    this->m_threadStatus = CAS_THREAD_INIT;
    this->m_decodeTask = new (std::nothrow) thread(DecodeTaskEntry, this);
    if (this->m_decodeTask == nullptr) {
        ERR("Decode task thread is null.");
        return -1;
    }

    if (this->m_decodeTask->joinable()) {
        this->m_decodeTask->detach();
    }

    this->m_threadStatus = CAS_THREAD_RUNNING;
    return 0;
}

int CasVideoHDecodeThread::Restart()
{
    if (this->m_threadStatus == CAS_THREAD_STOP) {
        this->m_threadStatus = CAS_THREAD_RUNNING;
        return 0;
    }
    return -1;
}

int CasVideoHDecodeThread::Stop()
{
    if (this->m_threadStatus == CAS_THREAD_RUNNING) {
        this->m_threadStatus = CAS_THREAD_STOP;
        return 0;
    }
    return -1;
}

int CasVideoHDecodeThread::Exit()
{
    if (this->m_threadStatus == CAS_THREAD_RUNNING || this->m_threadStatus == CAS_THREAD_STOP) {
        if (this->m_threadStatus != CAS_THREAD_EXIT) {
            this->m_threadStatus = CAS_THREAD_EXIT;
        }
        while (this->m_threadStatus == CAS_THREAD_EXIT) {
            usleep(1000);
        }
        if (this->m_videoEngine != nullptr) {
            this->m_videoEngine->StopDecoder();
            this->m_videoEngine->DestroyDecoder();
        }
        if (this->m_firstVideoFrame != nullptr) {
            delete this->m_firstVideoFrame;
            this->m_firstVideoFrame = nullptr;
        }
        return 0;
    }
    INFO("Video decode thread exit.");
    return -1;
}